import csv
import struct
import json
import os
import shutil
from typing import Iterator
from xml.dom import minidom


class FileUtil:
    @staticmethod
    def join(__path, *paths) -> str:
        return os.path.join(__path, *paths)

    @staticmethod
    def app_path(*paths) -> str:
        return os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", *paths)

    @staticmethod
    def get_path(file_path: str) -> str:
        return os.path.join(os.getcwd(), file_path)

    @staticmethod
    def exists(file_path: str) -> bool:
        return os.path.exists(file_path)

    @staticmethod
    def isfile(file_path: str) -> bool:
        return os.path.isfile(file_path)

    @staticmethod
    def get_files(dir_path: str, ext: str) -> list[str]:
        res = []
        for dir_path, _, file_names in os.walk(dir_path):
            for f in file_names:
                if f.lower().endswith(ext):
                    res.append(f"{dir_path}/{f}")

    @staticmethod
    def get_files_iter(base_dir: str, ext: str) -> Iterator[str]:
        for dir_path, _, file_names in os.walk(base_dir):
            for f in file_names:
                if f.lower().endswith(ext):
                    yield os.path.join(dir_path, f)

    @staticmethod
    def make_parent(file_path: str):
        dir_path = os.path.dirname(file_path)
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)

    def rm_dir(dir_path: str):
        if os.path.exists(dir_path):
            shutil.rmtree(dir_path)

    @staticmethod
    def read_text_file(file_path: str, encoding: str = "utf8") -> str:
        with open(
            file_path, "r", encoding=encoding, errors="backslashreplace"
        ) as text_file:
            data = text_file.read()

        return data

    @staticmethod
    def read_text_file_lines(fileName: str) -> list[str]:
        with open(
            fileName, "r", encoding="utf-8", errors="backslashreplace"
        ) as text_file:
            data = text_file.readlines()
            return data

    @staticmethod
    def write_text_file(file_path: str, content: str, encoding: str = "utf8"):
        FileUtil.make_parent(file_path)
        with open(
            file_path, "w", encoding=encoding, errors="backslashreplace"
        ) as text_file:
            text_file.write(content)

    @staticmethod
    def append_text_file(file_path: str, content: str, encoding: str = "utf8"):
        FileUtil.make_parent(file_path)
        with open(
            file_path,
            "a",
            encoding=encoding,
        ) as text_file:
            text_file.write(content)

    @staticmethod
    def write_text_file_if_change(file_path: str, content: str, encoding: str = "utf8"):
        if os.path.exists(file_path):
            with open(file_path, "r", encoding="utf-8") as f:
                old_content = f.read()

            if old_content == content:
                return
        print(f"write file {file_path}")
        FileUtil.write_text_file(file_path, content, encoding)

    @staticmethod
    def read_json_file(file_path: str, encoding: str = "utf8") -> any:
        with open(
            file_path, "r", encoding=encoding, errors="backslashreplace"
        ) as text_file:
            data = json.load(text_file)

        return data

    @staticmethod
    def write_json_file(file_path: str, content: dict, encoding: str = "utf8"):
        FileUtil.make_parent(file_path)
        with open(
            file_path, "w", encoding=encoding, errors="backslashreplace"
        ) as text_file:
            json.dump(content, text_file, ensure_ascii=False, indent=2, allow_nan=False)

    @staticmethod
    def read_bin_file(file_path: str):
        with open(file_path, "rb") as file:
            data = file.read()
        return data

    @staticmethod
    def read_csv_file(
        file_path: str, newline: str = "", delimiter: str = "\t"
    ) -> list[list[float]]:
        data: list[list[float]] = []
        with open(file_path, "r", newline=newline) as f_in:
            reader = csv.reader(f_in, delimiter=delimiter)
            for row in reader:
                cells: list[float] = []
                for cell in row:
                    cells.append(float(cell))
                data.append(cells)
        return data

    @staticmethod
    def write_csv_file(file_path: str, content: list[list[any]], newline: str = ""):
        FileUtil.make_parent(file_path)
        with open(file_path, "w", newline=newline) as f:
            writer = csv.writer(f)
            for row in content:
                writer.writerow(row)

    @staticmethod
    def read_bin_file_to_points(bin: str) -> list[tuple[float]]:
        result: list[tuple[float]] = []
        with open(bin, "rb") as bin_file:
            while True:
                b = bin_file.read(12)
                if not b:
                    break
                result.append(struct.unpack("3f", b))
        return result

    @staticmethod
    def read_asc_to_points(txt: str, sep: str = " ") -> list[tuple[float]]:
        result: list[tuple[float]] = []
        with open(txt, "r") as text_file:
            for line in text_file:
                if line.startswith("#"):
                    continue
                if len(line) == 0:
                    continue
                cells = line.split(sep)
                point = []
                for cell in cells:
                    point.append(float(cell))
                result.append(point)
        return result

    @staticmethod
    def read_xml_file(file_name: str) -> dict:
        content = {}
        doc: minidom.Document = minidom.parse(file_name)

        # 得到文档元素对象
        FileUtil.fill_xml_element(doc.documentElement, content)
        return content

    @staticmethod
    def fill_xml_element(element: minidom.Element, parent: dict):
        is_list: bool = False
        parent_name = element.nodeName

        # 判断这个元素是不是List
        # 如果元素的孩子名 = 元素名 + s，这个元素是List
        # 如果元素有多个孩子名，且名字相同，这个元素是List
        if element.childNodes:
            first_name = ""
            second_name = ""
            child: minidom.Node
            for child in element.childNodes:
                if child.nodeType == minidom.Node.ELEMENT_NODE:
                    if first_name == "":
                        first_name = child.nodeName
                    else:
                        second_name = child.nodeName
                        break

            if first_name + "s" == parent_name:
                is_list = True
            elif len(second_name) > 0 and first_name == second_name:
                is_list = True

        if is_list:
            items = []
            parent[parent_name] = items
            child: minidom.Node
            for child in element.childNodes:
                if child.nodeType == minidom.Node.ELEMENT_NODE:
                    item = {}
                    items.append(item)
                    FileUtil.fill_xml_element(child, item)
        else:
            item = {}
            parent[parent_name] = item
            if element.attributes:
                for i in range(element.attributes.length):
                    attr: minidom.Attr = element.attributes.item(i)
                    item[attr.name] = attr.value

            child: minidom.Node
            for child in element.childNodes:
                if child.nodeType == minidom.Node.ELEMENT_NODE:
                    FileUtil.fill_xml_element(child, item)
